#include <stdio.h>
#include <string.h>
#include <time.h>


// #include "jpegdecapi.h"
#include "dwl.h"
#include "vpu_global.h"

#include "hw_jpegdecapi.h"

#include "hw_jpeg_decode_api.h"

#define LOG_TAG "hw_jpeg_decode_api"

#ifdef DEBUG
    #define LocalPrint(_outfd_, _fmt_, ...) fprintf(_outfd_, "%s:%d\t\t" _fmt_ "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__)
    // #define LocalPrint(_fd_, _fmt_, ...) __android_log_print(6,LOG_TAG,"%s:%d\t\t" _fmt_, __FUNCTION__, __LINE__, ##__VA_ARGS__)

    void JpegDecTrace(const char *string)
    {
        LocalPrint(stdout, "%s\n", string);
    }

#else
    #define LocalPrint(...)
#endif


void PrintRet(JpegDecRet pJpegRet);
int dump_jpeg_file(char* file_name, char* jpeg_image, int flen);

typedef struct{
    JpegDecInst jpegInst;
    JpegDecInput jpegIn;
    JpegDecOutput jpegOut;
    VPUMemLinear_t streamMem;
    int streamMemLen;
    JpegDecImageInfo imageInfo;
    int valid;
    int isSUPPORTED;
}JpegHandle;

void* hw_jpeg_decoder_init(){
	JpegHandle *context;
	JpegDecRet ret;
	JpegDecApiVersion v;
	
    context = malloc(sizeof(JpegHandle));
    android_memset(context, 0, sizeof(JpegHandle));

	v = JpegGetAPIVersion();
	LocalPrint(stdout,"version: %d.%d\n", v.major, v.minor);

	ret = JpegDecInit(&context->jpegInst);
	if(ret != JPEGDEC_OK){
        PrintRet(ret);
		LocalPrint(stdout,"%d\terror:%d\n",__LINE__, ret);
        free(context);
		return NULL;
	}

	return context;
}

void hw_jpeg_decoder_deinit(void* jpegInst){
    JpegHandle* context=(JpegHandle*)jpegInst;
    if(context->valid){
        if(context->streamMem.vir_addr != NULL){
            VPUFreeLinear(&context->streamMem);
        }
    }

	JpegDecRelease(context->jpegInst);
    android_memset(context, 0, sizeof(JpegHandle));
    free(context);
}

int hw_jpeg_decoder_set_src(void* jpegInst, char* image, int flen){
    JpegHandle* context=(JpegHandle*)jpegInst;
    JpegDecRet ret;    
    
    LocalPrint(stderr, "hw_jpeg_decoder_set_src in:%p, len:%d\n", image, flen);
    if(!image || flen<=0){
        LocalPrint(stderr, "hw_jpeg_decoder_set_src invalid src.\n");
        return -1;
    }

    //invlide instance
    context->valid = 0;

    if(context->streamMemLen != flen && context->streamMem.vir_addr != NULL){
        VPUFreeLinear(&context->streamMem);
        context->streamMem.vir_addr = NULL;
        context->streamMem.phy_addr = 0;
        context->streamMemLen = 0;
    }

    if(context->streamMem.vir_addr == NULL){
        if(VPUMallocLinear(&context->streamMem, flen) != DWL_OK)
        {
            LocalPrint(stdout, "UNABLE TO ALLOCATE STREAM BUFFER MEMORY\n");
            return -1;
        }
    }
    context->streamMemLen = flen;

    /* memset input */
    android_memcpy(context->streamMem.vir_addr, image, flen);
    VPUMemFlush(&context->streamMem);
    // VPUMemInvalidate(&context->streamMem);

    android_memset(&context->jpegIn, 0, sizeof(JpegDecInput));
    /* initialize JpegDecDecode input structure */
    context->jpegIn.streamBuffer.pVirtualAddress = (uint32_t *) context->streamMem.vir_addr;
    context->jpegIn.streamBuffer.busAddress = context->streamMem.phy_addr;
    // context->jpegIn.bufferSize = flen;
    context->jpegIn.streamLength = flen;

    ret = JpegDecGetImageInfo(context->jpegInst, &context->jpegIn, &context->imageInfo);
    if(ret != JPEGDEC_OK){
        if(context->streamMem.vir_addr != NULL){
            VPUFreeLinear(&context->streamMem);
            context->streamMem.vir_addr = 0;
        }
        PrintRet(ret);
        LocalPrint(stdout,"%d\terror:%d\n",__LINE__, ret);

        // #ifdef DEBUG
        //     dump_jpeg_file("/sdcard/1.jpg", image, flen);
        //     exit(0);
        // #endif

        return -1;
    }

    if(context->imageInfo.thumbnailType == JPEGDEC_THUMBNAIL_JPEG)
    {
        /* decode thumbnail */
        LocalPrint(stdout, "\t-JPEG THUMBNAIL IN STREAM\n");
        LocalPrint(stdout, "\t-JPEG THUMBNAIL INFO\n");
        LocalPrint(stdout, "\t\t-JPEG thumbnail width: %d\n",
                context->imageInfo.outputWidthThumb);
        LocalPrint(stdout, "\t\t-JPEG thumbnail height: %d\n",
                context->imageInfo.outputHeightThumb);

        /* stream type */
        switch (context->imageInfo.codingModeThumb)
        {
        case JPEGDEC_BASELINE:
            LocalPrint(stdout, "\t\t-JPEG: STREAM TYPE: JPEGDEC_BASELINE\n");
            break;
        case JPEGDEC_PROGRESSIVE:
            LocalPrint(stdout, "\t\t-JPEG: STREAM TYPE: JPEGDEC_PROGRESSIVE\n");
            break;
        case JPEGDEC_NONINTERLEAVED:
            LocalPrint(stdout, "\t\t-JPEG: STREAM TYPE: JPEGDEC_NONINTERLEAVED\n");
            break;
        }

        if(context->imageInfo.outputFormatThumb)
        {
            switch (context->imageInfo.outputFormatThumb)
            {
            case JPEGDEC_YCbCr400:
                LocalPrint(stdout,
                        "\t\t-JPEG: THUMBNAIL OUTPUT: JPEGDEC_YCbCr400\n");
                break;
            case JPEGDEC_YCbCr420_SEMIPLANAR:
                LocalPrint(stdout,
                        "\t\t-JPEG: THUMBNAIL OUTPUT: JPEGDEC_YCbCr420_SEMIPLANAR\n");
                break;
            case JPEGDEC_YCbCr422_SEMIPLANAR:
                LocalPrint(stdout,
                        "\t\t-JPEG: THUMBNAIL OUTPUT: JPEGDEC_YCbCr422_SEMIPLANAR\n");
                break;
            case JPEGDEC_YCbCr440:
                LocalPrint(stdout,
                        "\t\t-JPEG: THUMBNAIL OUTPUT: JPEGDEC_YCbCr440\n");
                break;
            case JPEGDEC_YCbCr411_SEMIPLANAR:
                LocalPrint(stdout,
                        "\t\t-JPEG: THUMBNAIL OUTPUT: JPEGDEC_YCbCr411_SEMIPLANAR\n");
                break;
            case JPEGDEC_YCbCr444_SEMIPLANAR:
                LocalPrint(stdout,
                        "\t\t-JPEG: THUMBNAIL OUTPUT: JPEGDEC_YCbCr444_SEMIPLANAR\n");
                break;
            }
        }
        context->jpegIn.decImageType = JPEGDEC_THUMBNAIL;
    }
    else if(context->imageInfo.thumbnailType == JPEGDEC_NO_THUMBNAIL)
    {
        /* decode full image */
        LocalPrint(stdout,
                "\t-NO THUMBNAIL IN STREAM ==> Decode full resolution image\n");
        context->jpegIn.decImageType = JPEGDEC_IMAGE;
    }
   

    LocalPrint(stdout, "\t-JPEG FULL RESOLUTION INFO\n");
    LocalPrint(stdout, "\t\t-JPEG width: %d\n", context->imageInfo.outputWidth);
    LocalPrint(stdout, "\t\t-JPEG height: %d\n", context->imageInfo.outputHeight);
    LocalPrint(stdout, "\t\t-JPEG displayWidth: %d\n", context->imageInfo.displayWidth);
    LocalPrint(stdout, "\t\t-JPEG displayheight: %d\n", context->imageInfo.displayHeight);

    if(context->imageInfo.outputFormat)
    {
        switch (context->imageInfo.outputFormat)
        {
        case JPEGDEC_YCbCr400:
            LocalPrint(stdout,
                    "\t\t-JPEG: FULL RESOLUTION OUTPUT: JPEGDEC_YCbCr400\n");
            break;
        case JPEGDEC_YCbCr420_SEMIPLANAR:
            LocalPrint(stdout,
                    "\t\t-JPEG: FULL RESOLUTION OUTPUT: JPEGDEC_YCbCr420_SEMIPLANAR\n");
            break;
        case JPEGDEC_YCbCr422_SEMIPLANAR:
            LocalPrint(stdout,
                    "\t\t-JPEG: FULL RESOLUTION OUTPUT: JPEGDEC_YCbCr422_SEMIPLANAR\n");
            break;
        case JPEGDEC_YCbCr440:
            LocalPrint(stdout,
                    "\t\t-JPEG: FULL RESOLUTION OUTPUT: JPEGDEC_YCbCr440\n");
            break;
        case JPEGDEC_YCbCr411_SEMIPLANAR:
            LocalPrint(stdout,
                    "\t\t-JPEG: FULL RESOLUTION OUTPUT: JPEGDEC_YCbCr411_SEMIPLANAR\n");
            break;
        case JPEGDEC_YCbCr444_SEMIPLANAR:
            LocalPrint(stdout,
                    "\t\t-JPEG: FULL RESOLUTION OUTPUT: JPEGDEC_YCbCr444_SEMIPLANAR\n");
            break;
        }
    }

    /* stream type */
    switch (context->imageInfo.codingMode)
    {
    case JPEGDEC_BASELINE:
        LocalPrint(stdout, "\t\t-JPEG: STREAM TYPE: JPEGDEC_BASELINE\n");
        break;
    case JPEGDEC_PROGRESSIVE:
        LocalPrint(stdout, "\t\t-JPEG: STREAM TYPE: JPEGDEC_PROGRESSIVE\n");
        break;
    case JPEGDEC_NONINTERLEAVED:
        LocalPrint(stdout, "\t\t-JPEG: STREAM TYPE: JPEGDEC_NONINTERLEAVED\n");
        break;
    }

    if(context->imageInfo.thumbnailType == JPEGDEC_THUMBNAIL_JPEG)
    {
        LocalPrint(stdout, "\t-JPEG ThumbnailType: JPEG\n");
    }
    else if(context->imageInfo.thumbnailType == JPEGDEC_NO_THUMBNAIL)
        LocalPrint(stdout, "\t-JPEG ThumbnailType: NO THUMBNAIL\n");
    else if(context->imageInfo.thumbnailType == JPEGDEC_THUMBNAIL_NOT_SUPPORTED_FORMAT)
        LocalPrint(stdout, "\t-JPEG ThumbnailType: NOT SUPPORTED THUMBNAIL\n");

    LocalPrint(stdout, "PHASE 3: GET IMAGE INFO successful\n");
    
     if((context->jpegIn.decImageType == JPEGDEC_THUMBNAIL &&
        context->imageInfo.codingModeThumb == JPEGDEC_PROGRESSIVE) ||
       (context->jpegIn.decImageType == JPEGDEC_IMAGE &&
        context->imageInfo.codingMode == JPEGDEC_PROGRESSIVE))
        context->jpegIn.sliceMbSet = 0;

    context->valid = 1;
    return 0;
}


/*
    JpegDecInput jpegIn;
    JpegDecOutput jpegOut;
    VPUMemLinear_t streamMem;
    JpegDecImageInfo imageInfo;
*/

int hw_jpeg_decoder_getsize(void* jpegInst, int *w, int* h)
{
    JpegHandle* context=(JpegHandle*)jpegInst;
    if(!context->valid){
        *w=*h=0;
        return -1;
    }
	
	*w = context->imageInfo.displayWidth;
	*h = context->imageInfo.displayHeight; 
	return 0;
}

int hw_jpeg_decoder_on_frame(void* jpegInst,void *rgba){
	JpegHandle* context=(JpegHandle*)jpegInst;
	JpegDecRet ret;

    if(!context->valid) {
        LocalPrint(stdout, "%s:invalid jpegInst context.\n", __FUNCTION__);
        return -1;
    }

    uint32_t ppScaleW = context->imageInfo.displayWidth;
    uint32_t ppScaleH = context->imageInfo.displayHeight;
    
    // int outWidth = context->imageInfo.outputWidth;
    // int outHeight = context->imageInfo.outputHeight;
    uint32_t outWidth = context->imageInfo.displayWidth;
    uint32_t outHeight = context->imageInfo.displayHeight;
    VPUMemLinear_t streamMemOutBuf;
    uint32_t outLen = outWidth*outHeight*4;
    if(VPUMallocLinear(&streamMemOutBuf, outLen) != DWL_OK)
    {
        LocalPrint(stdout, "UNABLE TO ALLOCATE STREAM BUFFER MEMORY\n");
        return -1;
    }

	android_memset(&context->jpegOut, 0, sizeof(JpegDecOutput));	
	LocalPrint(stdout, "[%d]\t\tStart JpegDecDecode\n", __LINE__);

	ret = JpegDecDecode(context->jpegInst, &context->jpegIn, &context->jpegOut);
    if(ret == JPEGDEC_FRAME_READY && context->imageInfo.outputFormat == JPEGDEC_YCbCr420_SEMIPLANAR){

        LocalPrint(stdout,"jpegOut:%p %p %p\n", context->jpegOut.outputPictureY.pVirtualAddress, context->jpegOut.outputPictureCbCr.pVirtualAddress,context->jpegOut.outputPictureCr.pVirtualAddress);

        static struct timeval tpstart,tpend;
        float timeuse; 
        gettimeofday(&tpstart,NULL); 

        void Nv21Convert2RGBA(uint32_t* rgbs, uint8_t* yuv_y,uint8_t* yuv_uv, int width, int height);
        Nv21Convert2RGBA(rgba, context->jpegOut.outputPictureY.pVirtualAddress,  context->jpegOut.outputPictureCbCr.pVirtualAddress, ppScaleW, ppScaleH);

        gettimeofday(&tpend,NULL); 
        timeuse=1000000*(tpend.tv_sec-tpstart.tv_sec) + tpend.tv_usec-tpstart.tv_usec; 
        LocalPrint(stdout, "Convert Used Time:%f ms\n",timeuse/=1000.0);

    }
    else
    {
        PrintRet(ret);
        goto end;            
    }

	LocalPrint(stdout, "[%d]\t\tJpegDecDecode done.\n", __LINE__);

end:
    if(streamMemOutBuf.vir_addr){
        VPUFreeLinear(&streamMemOutBuf);
    }
   	LocalPrint(stdout, "[%d]\t\tMain loop exit.\n", __LINE__);
    return 0;
}

void PrintRet(JpegDecRet pJpegRet)
{

    switch (pJpegRet)
    {
    case JPEGDEC_FRAME_READY:
        LocalPrint(stdout,"TB: jpeg API returned : JPEGDEC_FRAME_READY\n");
        break;
    case JPEGDEC_OK:
        LocalPrint(stdout,"TB: jpeg API returned : JPEGDEC_OK\n");
        break;
    case JPEGDEC_ERROR:
        LocalPrint(stdout,"TB: jpeg API returned : JPEGDEC_ERROR\n");
        break;
    case JPEGDEC_DWL_HW_TIMEOUT:
        LocalPrint(stdout,"TB: jpeg API returned : JPEGDEC_HW_TIMEOUT\n");
        break;
    case JPEGDEC_UNSUPPORTED:
        LocalPrint(stdout,"TB: jpeg API returned : JPEGDEC_UNSUPPORTED\n");
        break;
    case JPEGDEC_PARAM_ERROR:
        LocalPrint(stdout,"TB: jpeg API returned : JPEGDEC_PARAM_ERROR\n");
        break;
    case JPEGDEC_MEMFAIL:
        LocalPrint(stdout,"TB: jpeg API returned : JPEGDEC_MEMFAIL\n");
        break;
    case JPEGDEC_INITFAIL:
        LocalPrint(stdout,"TB: jpeg API returned : JPEGDEC_INITFAIL\n");
        break;
    case JPEGDEC_HW_BUS_ERROR:
        LocalPrint(stdout,"TB: jpeg API returned : JPEGDEC_HW_BUS_ERROR\n");
        break;
    case JPEGDEC_SYSTEM_ERROR:
        LocalPrint(stdout,"TB: jpeg API returned : JPEGDEC_SYSTEM_ERROR\n");
        break;
    case JPEGDEC_DWL_ERROR:
        LocalPrint(stdout,"TB: jpeg API returned : JPEGDEC_DWL_ERROR\n");
        break;
    case JPEGDEC_INVALID_STREAM_LENGTH:
        LocalPrint(stdout,"TB: jpeg API returned : JPEGDEC_INVALID_STREAM_LENGTH\n");
        break;
    case JPEGDEC_STRM_ERROR:
        LocalPrint(stdout,"TB: jpeg API returned : JPEGDEC_STRM_ERROR\n");
        break;
    case JPEGDEC_INVALID_INPUT_BUFFER_SIZE:
        LocalPrint(stdout,"TB: jpeg API returned : JPEGDEC_INVALID_INPUT_BUFFER_SIZE\n");
        break;
    case JPEGDEC_INCREASE_INPUT_BUFFER:
        LocalPrint(stdout,"TB: jpeg API returned : JPEGDEC_INCREASE_INPUT_BUFFER\n");
        break;
    case JPEGDEC_SLICE_MODE_UNSUPPORTED:
        LocalPrint(stdout, "TB: jpeg API returned : JPEGDEC_SLICE_MODE_UNSUPPORTED\n");
        break;
    default:
        LocalPrint(stdout,"TB: jpeg API returned unknown status\n");
        break;
    }
}

int dump_jpeg_file(char* file_name, char* jpeg_image, int flen){
    FILE *fIn = fopen(file_name, "wb");
    if(fIn == NULL)
    {
        LocalPrint(stdout, "Unable to open input file\n");
        exit(0);
    }

    int ret = fwrite(jpeg_image, sizeof(uint8_t), flen, fIn);
    fclose(fIn);
    return ret;
}
